home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue66 / SQLComp / DMSQLBase.pas next >
Encoding:
Pascal/Delphi Source File  |  2000-09-20  |  18.7 KB  |  607 lines

  1. unit DMSQLBase;
  2.  
  3. {
  4.   Features not implemented for simplicity:
  5.   - Support for the UNION clause.
  6.   - Criteria on HAVING clause.
  7.   - Definition of a set of valid operators for each criterion (currently it is
  8.     possible to create senseless combinations).
  9. }
  10.  
  11. interface
  12.  
  13. uses
  14.   Classes, DB;
  15.  
  16. type
  17.   // Supported SQL operators.
  18.   TSQLOperator = (opEqualTo, opGreaterThan, opLessThan, opGreaterThanOrEqualTo,
  19.     opLessThanOrEqualTo, opNotEqualTo, opBeginningWith, opEndingWith,
  20.     opContaining);
  21.  
  22.   // Criterion options.
  23.   // sqlOverrideFieldType: if this option is set, the component employs the
  24.   //   value of the DataType property in building the SQL string; otherwise
  25.   //   the data type is determined by going up the DataSource/DataField chain
  26.   //   to the Field object (this works only if persistent fields are defined).
  27.   // sqlAddBrackets: the criterion is enclosed in round brackets.
  28.   TSQLCriterionOption = (sqlOverrideFieldType, sqlAddBrackets);
  29.   TSQLCriterionOptions = set of TSQLCriterionOption;
  30.  
  31. const
  32.   SQLCriterionOptionsDefault = [sqlOverrideFieldType, sqlAddBrackets];
  33.  
  34. type
  35.   // Options for the query component.
  36.   // sqlOpenAfterBuild: the query is opened after each BuildSQL call.
  37.   TSQLOption = (sqlOpenAfterBuild);
  38.   TSQLOptions = set of TSQLOption;
  39.  
  40. const
  41.   SQLOptionsDefault = [];
  42.  
  43. type
  44.   // Operator for the connection of criteria.
  45.   TSQLConnector = (scAnd, scOr);
  46.  
  47. const
  48.   SQLConnectorDefault = scAnd;
  49.  
  50. type
  51.   // Every criterion component must implement this interface.
  52.   IDMSQLCriterion = interface
  53.     ['{8967F222-FD6F-11D3-BA36-CA3BD3000000}']
  54.     // Returns the complete SQL expression relative to the criterion.
  55.     function GetSQL: string;
  56.     // Clears the criterion.
  57.     procedure ClearSQL;
  58.   end;
  59.  
  60.   // Used by a TDMSQLCriterion object to read information from the
  61.   // owning object.
  62.   IDMSQLCriterionData = interface
  63.     ['{8967F223-FD6F-11D3-BA36-CA3BD3000000}']
  64.     // Returns the value(s) of the criterion; Unassigned means no chosen
  65.     // criterion.
  66.     function GetSQLValue: Variant;
  67.   end;
  68.  
  69.   // A component may delegate the implementation of IDMSQLCriterion to an
  70.   // object of this class or of a derived one.
  71.   TDMSQLCustomCriterion = class(TPersistent)
  72.   private
  73.     FOwner: IDMSQLCriterionData;
  74.     FDataSource: TDataSource;
  75.     FDataField: string;
  76.     FDataType: TFieldType;
  77.     FSQLOperator: TSQLOperator;
  78.     FSQLOptions: TSQLCriterionOptions;
  79.   protected
  80.     function GetOpeningBracket: string;
  81.     function GetClosingBracket: string;
  82.     function GetDataSource: TDataSource; virtual;
  83.     function GetDataField: string; virtual;
  84.     function GetDataType: TFieldType; virtual;
  85.     function GetSQLOperator: TSQLOperator; virtual;
  86.     function GetSQLOptions: TSQLCriterionOptions; virtual;
  87.     procedure SetSQLOperator(Value: TSQLOperator); virtual;
  88.     procedure SetDataType(const Value: TFieldType); virtual;
  89.     function EncodeSQLOpValue(AOperator: TSQLOperator; AValue: Variant;
  90.       ADataType: TFieldType): string;
  91.     function EncodeSQLValue(AValue: Variant; ADataType: TFieldType;
  92.       AddBefore: string = ''; AddAfter: string = ''): string;
  93.     // Builds the SQL string from the parameters.
  94.     // AValue indicates the field value; Null means a NULL value, Unassigned
  95.     // an unspecified value (which will produce a Result of '').
  96.     function BuildSQL(AFieldName: string; ADataType: TFieldType;
  97.       AOperator: TSQLOperator; AValue: Variant): string; virtual; abstract;
  98.     property DataSource: TDataSource read GetDataSource write FDataSource;
  99.     property DataField: string read GetDataField write FDataField;
  100.     property DataType: TFieldType read GetDataType write SetDataType
  101.       default ftString;
  102.     property SQLOperator: TSQLOperator read GetSQLOperator write SetSQLOperator
  103.       default opEqualTo;
  104.     property SQLOptions: TSQLCriterionOptions read GetSQLOptions write FSQLOptions
  105.       default SQLCriterionOptionsDefault;
  106.   public
  107.     constructor Create(AOwner: IDMSQLCriterionData);
  108.     // Builds and returns the SQL string for the owning criterion.
  109.     function GetSQL: string;
  110.     procedure CustomNotification(AComponent: TComponent; Operation: TOperation);
  111.   published
  112.   end;
  113.  
  114.   // A single criterion (f. ex. for a TEdit).
  115.   TDMSQLSingleCriterion = class(TDMSQLCustomCriterion)
  116.   protected
  117.     function BuildSQL(AFieldName: string; ADataType: TFieldType;
  118.       AOperator: TSQLOperator; AValue: Variant): string; override;
  119.   published
  120.     property DataSource;
  121.     property DataField;
  122.     property DataType;
  123.     property SQLOperator;
  124.     property SQLOptions;
  125.   end;
  126.  
  127.   // A multi-select criterion (f. ex. for a TListBox).
  128.   TDMSQLMultipleCriterion = class(TDMSQLCustomCriterion)
  129.   protected
  130.     function BuildSQL(AFieldName: string; ADataType: TFieldType;
  131.       AOperator: TSQLOperator; AValue: Variant): string; override;
  132.   published
  133.     property DataSource;
  134.     property DataField;
  135.     property DataType;
  136.     property SQLOptions;
  137.   end;
  138.  
  139.   // Used for BuildSQL; the parameters are a reference to an object implementing
  140.   // the IDMSQLCriterion interface plus a flag that indicates whether the
  141.   // callback procedure must be called again or not.
  142.   TDMSQLCriteriaEnumProc = procedure (var Criterion: IDMSQLCriterion; var IsLast: Boolean) of object;
  143.  
  144.   TDMSQLQueryImpl = class;
  145.  
  146.   // Identifies a query object in our framework.
  147.   IDMSQLQuery = interface
  148.     ['{ABE29522-FDD0-11D3-BA36-CA3BD3000000}']
  149.     // These two methods are called by the TDMSQLQueryImpl object before and
  150.     // after each call to BuildSQL.
  151.     procedure BeforeBuild(Sender: TDMSQLQueryImpl);
  152.     procedure AfterBuild(Sender: TDMSQLQueryImpl);
  153.     // This method is called by the TDMSQLQueryImpl after a BuildSQL (and before
  154.     // AfterBuild) to set the newly built SQL statement. Usually, a query object
  155.     // will assign the value to its SQL property.
  156.     procedure SetSQLText(Value: string);
  157.   end;
  158.  
  159.   TDMSQLQueryImpl = class(TPersistent)
  160.   private
  161.     FOwner: IDMSQLQuery;
  162.     FBaseSQL: TStrings;
  163.     FBuiltSQL: TStrings;
  164.     FSQLOptions: TSQLOptions;
  165.     FSQLConnector: TSQLConnector;
  166.     procedure DoAfterBuild;
  167.     procedure DoBeforeBuild;
  168.     procedure AddCriterion(Value: IDMSQLCriterion);
  169.     procedure ClearCriterion(Value: IDMSQLCriterion);
  170.     function GetSQLOptions: TSQLOptions;
  171.     procedure SetSQLConnector(const Value: TSQLConnector);
  172.     procedure SetBaseSQL(const Value: TStrings);
  173.     procedure MergeBuiltSQL;
  174.     procedure PurgeAddedCriteria(SQLText: TStrings);
  175.     function GetSQLConnectorAsString: string;
  176.   public
  177.     constructor Create(AOwner: IDMSQLQuery);
  178.     destructor Destroy; override;
  179.     // The base SQL statement; this property must be assigned a value at least
  180.     // once by the query object (f. ex. in the Loaded method).
  181.     property BaseSQL: TStrings read FBaseSQL write SetBaseSQL;
  182.     // Methods to build the SQL string from:
  183.     // - an array of criteria.
  184.     procedure BuildSQL(Criteria: array of IDMSQLCriterion); overload;
  185.     // - all the criteria owned by AOwner.
  186.     procedure BuildSQL(AOwner: TComponent); overload;
  187.     // - all the criteria passed by the callback function.
  188.     procedure BuildSQL(EnumProc: TDMSQLCriteriaEnumProc); overload;
  189.     // Methods to clear the criteria.
  190.     procedure ClearCriteria(Criteria: array of IDMSQLCriterion); overload;
  191.     procedure ClearCriteria(AOwner: TComponent); overload;
  192.     procedure ClearCriteria(EnumProc: TDMSQLCriteriaEnumProc); overload;
  193.   published
  194.     property SQLOptions: TSQLOptions read GetSQLOptions write FSQLOptions
  195.       default SQLOptionsDefault;
  196.     property SQLConnector: TSQLConnector read FSQLConnector write SetSQLConnector
  197.       default SQLConnectorDefault;
  198.   end;
  199.  
  200. const
  201.   // Not all the data types are supported for applying a filter.
  202.   SupportedDataTypes = [ftString, ftSmallint, ftInteger, ftWord, ftFloat,
  203.     ftCurrency, ftBCD, ftDate, ftTime, ftDateTime, ftAutoInc, ftFixedChar,
  204.     ftWideString, ftLargeint];
  205.  
  206. var
  207.   SQLQuote: Char = '''';
  208.  
  209. implementation
  210.  
  211. uses
  212.   SysUtils, DMSQLUtils;
  213.  
  214. { TDMSQLCustomCriterion }
  215.  
  216. constructor TDMSQLCustomCriterion.Create(AOwner: IDMSQLCriterionData);
  217. begin
  218.   inherited Create;
  219.   FOwner := AOwner;
  220.   FSQLOperator := opEqualTo;
  221.   FDataType := ftString;
  222.   FSQLOptions := SQLCriterionOptionsDefault;
  223. end;
  224.  
  225. function TDMSQLCustomCriterion.GetSQL: string;
  226. var
  227.   TheField: TField;
  228. begin
  229.   Result := '';
  230.   if GetDataField() <> '' then begin
  231.     if sqlOverrideFieldType in GetSQLOptions() then
  232.       Result := BuildSQL(GetDataField(), GetDataType(), GetSQLOperator(), FOwner.GetSQLValue())
  233.     else begin
  234.       if (GetDataSource() <> nil) and (GetDataSource().DataSet <> nil) then begin
  235.         TheField := GetDataSource().DataSet.FindField(GetDataField());
  236.         if TheField <> nil then
  237.           Result := BuildSQL(TheField.FieldName, TheField.DataType, GetSQLOperator(), FOwner.GetSQLValue());
  238.       end;
  239.     end;
  240.   end;
  241. end;
  242.  
  243. function TDMSQLCustomCriterion.EncodeSQLOpValue(AOperator: TSQLOperator; AValue: Variant; ADataType: TFieldType): string;
  244. begin
  245.   case AOperator of
  246.     opEqualTo: Result := '= ' + EncodeSQLValue(AValue, ADataType);
  247.     opGreaterThan: Result := '> ' + EncodeSQLValue(AValue, ADataType);
  248.     opLessThan: Result := '< ' + EncodeSQLValue(AValue, ADataType);
  249.     opGreaterThanOrEqualTo: Result := '>= ' + EncodeSQLValue(AValue, ADataType);
  250.     opLessThanOrEqualTo: Result := '<= ' + EncodeSQLValue(AValue, ADataType);
  251.     opNotEqualTo: Result := '<> ' + EncodeSQLValue(AValue, ADataType);
  252.     opBeginningWith: Result := 'like ' + EncodeSQLValue(AValue, ADataType, '', '%');
  253.     opEndingWith: Result := 'like ' + EncodeSQLValue(AValue, ADataType, '%', '');
  254.     opContaining: Result := 'like ' + EncodeSQLValue(AValue, ADataType, '%', '%');
  255.   end;
  256. end;
  257.  
  258. function TDMSQLCustomCriterion.EncodeSQLValue(AValue: Variant; ADataType: TFieldType;
  259.   AddBefore: string = ''; AddAfter: string = ''): string;
  260. begin
  261.   Result := '';
  262.   case ADataType of
  263.     ftString, ftFixedChar, ftWideString: begin
  264.       Result := SQLQuote + AddBefore + AValue + AddAfter + SQLQuote;
  265.     end;
  266.     ftSmallint, ftInteger, ftWord, ftAutoInc, ftLargeint: begin
  267.       Result := AValue;
  268.     end;
  269.     ftFloat, ftCurrency, ftBCD: begin
  270.       Result := AValue;
  271.     end;
  272.     ftDate, ftTime, ftDateTime: begin
  273.       Result := SQLQuote + FormatSQLDateTime(StrToDateTime(AValue)) + SQLQuote;
  274.     end;
  275.   end;
  276. end;
  277.  
  278. function TDMSQLCustomCriterion.GetOpeningBracket: string;
  279. begin
  280.   if sqlAddBrackets in GetSQLOptions() then
  281.     Result := '('
  282.   else
  283.     Result := '';
  284. end;
  285.  
  286. function TDMSQLCustomCriterion.GetClosingBracket: string;
  287. begin
  288.   if sqlAddBrackets in GetSQLOptions() then
  289.     Result := ')'
  290.   else
  291.     Result := '';
  292. end;
  293.  
  294. function TDMSQLCustomCriterion.GetDataField: string;
  295. begin
  296.   Result := FDataField;
  297. end;
  298.  
  299. procedure TDMSQLCustomCriterion.SetSQLOperator(Value: TSQLOperator);
  300. begin
  301.   if FSQLOperator <> Value then
  302.     FSQLOperator := Value;
  303. end;
  304.  
  305. function TDMSQLCustomCriterion.GetDataSource: TDataSource;
  306. begin
  307.   Result := FDataSource;
  308. end;
  309.  
  310. function TDMSQLCustomCriterion.GetDataType: TFieldType;
  311. begin
  312.   Result := FDataType;
  313. end;
  314.  
  315. function TDMSQLCustomCriterion.GetSQLOptions: TSQLCriterionOptions;
  316. begin
  317.   Result := FSQLOptions;
  318. end;
  319.  
  320. function TDMSQLCustomCriterion.GetSQLOperator: TSQLOperator;
  321. begin
  322.   Result := FSQLOperator;
  323. end;
  324.  
  325. procedure TDMSQLCustomCriterion.SetDataType(const Value: TFieldType);
  326. begin
  327.   if not (Value in SupportedDataTypes) then
  328.     raise Exception.Create('Field type not supported');
  329.   if FDataType <> Value then
  330.     FDataType := Value;
  331. end;
  332.  
  333. procedure TDMSQLCustomCriterion.CustomNotification(AComponent: TComponent; Operation: TOperation);
  334. begin
  335.   if Operation in [opRemove] then begin
  336.     if AComponent = FDataSource then
  337.       FDataSource := nil;
  338.   end;
  339. end;
  340.  
  341. { TDMSQLSingleCriterion }
  342.  
  343. function TDMSQLSingleCriterion.BuildSQL(AFieldName: string;
  344.   ADataType: TFieldType; AOperator: TSQLOperator; AValue: Variant): string;
  345. begin
  346.   if VarIsEmpty(AValue) then
  347.     // Unassigned
  348.     Result := ''
  349.   else if AValue = Null then begin
  350.     // Null
  351.     if AOperator = opEqualTo then
  352.       Result := GetOpeningBracket() + AFieldName + ' is null' + GetClosingBracket()
  353.     else
  354.       Result := GetOpeningBracket() + AFieldName + 'is not null' + GetClosingBracket();
  355.   end
  356.   else begin
  357.     // Other values
  358.     if ADataType in SupportedDataTypes then
  359.       Result := GetOpeningBracket() + AFieldName + ' ' +
  360.         EncodeSQLOpValue(AOperator, AValue, ADataType) + GetClosingBracket();
  361.   end;
  362. end;
  363.  
  364. { TDMSQLMultipleCriterion }
  365.  
  366. function TDMSQLMultipleCriterion.BuildSQL(AFieldName: string;
  367.   ADataType: TFieldType; AOperator: TSQLOperator; AValue: Variant): string;
  368. var
  369.   g: Integer;
  370.   CurrValue: Variant;
  371.   HighBound: Integer;
  372.   CurrStr: string;
  373. begin
  374.   if VarIsEmpty(AValue) then
  375.     // Unassigned
  376.     Result := ''
  377.   else if VarIsArray(AValue) then begin
  378.     HighBound := VarArrayHighBound(AValue, 1);
  379.     for g := VarArrayLowBound(AValue, 1) to HighBound do begin
  380.       CurrStr := '';
  381.       CurrValue := AValue[g];
  382.       if VarIsEmpty(CurrValue) then
  383.         Result := ''
  384.       else if CurrValue = Null then
  385.         // Null
  386.         CurrStr := GetOpeningBracket() + AFieldName + ' is null' + GetClosingBracket()
  387.       else begin
  388.         // Other values
  389.         if ADataType in SupportedDataTypes then
  390.           CurrStr := GetOpeningBracket() + AFieldName + ' ' +
  391.             EncodeSQLOpValue(AOperator, CurrValue, ADataType) + GetClosingBracket();
  392.       end;
  393.       if CurrStr <> '' then begin
  394.         Result := Result + CurrStr;
  395.         if g < HighBound then
  396.           Result := Result + ' or ';
  397.       end;
  398.     end;
  399.     if Result <> '' then
  400.       Result := GetOpeningBracket() + Result + GetClosingBracket();
  401.   end;
  402. end;
  403.  
  404. { TDMSQLQueryImpl }
  405.  
  406. constructor TDMSQLQueryImpl.Create(AOwner: IDMSQLQuery);
  407. begin
  408.   inherited Create;
  409.   FOwner := AOwner;
  410.   FSQLOptions := SQLOptionsDefault;
  411.   FSQLConnector := SQLConnectorDefault;
  412.   FBaseSQL := TStringList.Create;
  413.   FBuiltSQL := TStringList.Create;
  414. end;
  415.  
  416. destructor TDMSQLQueryImpl.Destroy;
  417. begin
  418.   FBaseSQL.Free;
  419.   FBuiltSQL.Free;
  420.   inherited;
  421. end;
  422.  
  423. procedure TDMSQLQueryImpl.BuildSQL(Criteria: array of IDMSQLCriterion);
  424. var
  425.   g: Integer;
  426. begin
  427.   DoBeforeBuild;
  428.   for g := Low(Criteria) to High(Criteria) do
  429.     AddCriterion(Criteria[g]);
  430.   DoAfterBuild;
  431. end;
  432.  
  433. procedure TDMSQLQueryImpl.BuildSQL(AOwner: TComponent);
  434. var
  435.   g: Integer;
  436.   CurrentCriterion: IDMSQLCriterion;
  437. begin
  438.   if Assigned(AOwner) then begin
  439.     DoBeforeBuild;
  440.     for g := 0 to Pred(AOwner.ComponentCount) do
  441.       if AOwner.Components[g].GetInterface(IDMSQLCriterion, CurrentCriterion) then
  442.         AddCriterion(CurrentCriterion);
  443.     DoAfterBuild;
  444.   end;
  445. end;
  446.  
  447. procedure TDMSQLQueryImpl.BuildSQL(EnumProc: TDMSQLCriteriaEnumProc);
  448. var
  449.   Finished: Boolean;
  450.   CurrentCriterion: IDMSQLCriterion;
  451. begin
  452.   if Assigned(EnumProc) then begin
  453.     DoBeforeBuild;
  454.     Finished := False;
  455.     repeat
  456.       CurrentCriterion := nil;
  457.       EnumProc(CurrentCriterion, Finished);
  458.       if Assigned(CurrentCriterion) then
  459.         AddCriterion(CurrentCriterion);
  460.     until Finished;
  461.     DoAfterBuild;
  462.   end;
  463. end;
  464.  
  465. procedure TDMSQLQueryImpl.ClearCriteria(Criteria: array of IDMSQLCriterion);
  466. var
  467.   g: Integer;
  468. begin
  469.   for g := Low(Criteria) to High(Criteria) do
  470.     ClearCriterion(Criteria[g]);
  471. end;
  472.  
  473. procedure TDMSQLQueryImpl.ClearCriteria(AOwner: TComponent);
  474. var
  475.   g: Integer;
  476.   CurrentCriterion: IDMSQLCriterion;
  477. begin
  478.   if Assigned(AOwner) then begin
  479.     for g := 0 to Pred(AOwner.ComponentCount) do
  480.       if AOwner.Components[g].GetInterface(IDMSQLCriterion, CurrentCriterion) then
  481.         ClearCriterion(CurrentCriterion);
  482.   end;
  483. end;
  484.  
  485. procedure TDMSQLQueryImpl.ClearCriteria(EnumProc: TDMSQLCriteriaEnumProc);
  486. var
  487.   Finished: Boolean;
  488.   CurrentCriterion: IDMSQLCriterion;
  489. begin
  490.   if Assigned(EnumProc) then begin
  491.     Finished := False;
  492.     repeat
  493.       CurrentCriterion := nil;
  494.       EnumProc(CurrentCriterion, Finished);
  495.       if Assigned(CurrentCriterion) then
  496.         ClearCriterion(CurrentCriterion);
  497.     until Finished;
  498.   end;
  499. end;
  500.  
  501. function TDMSQLQueryImpl.GetSQLConnectorAsString: string;
  502. begin
  503.   Result := '';
  504.   case FSQLConnector of
  505.     scAnd: Result := 'and';
  506.     scOr: Result := 'or';
  507.   end;
  508. end;
  509.  
  510. procedure TDMSQLQueryImpl.DoBeforeBuild;
  511. begin
  512.   FBuiltSQL.Clear;
  513.   if Assigned(FOwner) then
  514.     FOwner.BeforeBuild(Self);
  515. end;
  516.  
  517. procedure TDMSQLQueryImpl.DoAfterBuild;
  518. begin
  519.   MergeBuiltSQL;
  520.   if Assigned(FOwner) then
  521.     FOwner.AfterBuild(Self);
  522. end;
  523.  
  524. function TDMSQLQueryImpl.GetSQLOptions: TSQLOptions;
  525. begin
  526.   Result := FSQLOptions;
  527. end;
  528.  
  529. procedure TDMSQLQueryImpl.SetBaseSQL(const Value: TStrings);
  530. begin
  531.   FBaseSQL.Assign(Value);
  532. end;
  533.  
  534. procedure TDMSQLQueryImpl.SetSQLConnector(const Value: TSQLConnector);
  535. begin
  536.   if FSQLConnector <> Value then
  537.     FSQLConnector := Value;
  538. end;
  539.  
  540. procedure TDMSQLQueryImpl.AddCriterion(Value: IDMSQLCriterion);
  541. var
  542.   CriterionSQL: string;
  543. begin
  544.   CriterionSQL := Value.GetSQL();
  545.   if CriterionSQL <> '' then
  546.     FBuiltSQL.Add(CriterionSQL + ' ' + GetSQLConnectorAsString());
  547. end;
  548.  
  549. procedure TDMSQLQueryImpl.ClearCriterion(Value: IDMSQLCriterion);
  550. begin
  551.   Value.ClearSQL;
  552. end;
  553.  
  554. procedure TDMSQLQueryImpl.PurgeAddedCriteria(SQLText: TStrings);
  555. var
  556.   P: Integer;
  557.   S: string;
  558.   SQLConnStr: string;
  559.   N: Integer;
  560. begin
  561.   SQLConnStr := ' ' + GetSQLConnectorAsString();
  562.   N := SQLText.Count;
  563.   if N > 0 then begin
  564.     S := SQLText[Pred(N)];
  565.     if S <> '' then begin
  566.       P := Pos(SQLConnStr, S);
  567.       if P = Length(S) - (Length(SQLConnStr) - 1) then begin
  568.         System.Delete(S, P, Length(SQLConnStr));
  569.         SQLText[Pred(N)] := S;
  570.       end;
  571.     end;
  572.   end;
  573. end;
  574.  
  575. procedure TDMSQLQueryImpl.MergeBuiltSQL;
  576. var
  577.   P: Integer;
  578. begin
  579.   // Delete the extra connector from FBuiltSQL.
  580.   PurgeAddedCriteria(FBuiltSQL);
  581.   // FBuiltSQL contains the list, in SQL syntax, of the where expressions
  582.   // to add. FBaseSQL contains the basic statement.
  583.  
  584.   // If the basic statement already contains a WHERE clause, don't add one.
  585.   if FBuiltSQL.Count > 0 then begin
  586.     if ExtractWhereClause(FBaseSQL.Text) = '' then
  587.       FBuiltSQL.Insert(0, sqlWhere)
  588.     else
  589.       FBuiltSQL.Insert(0, 'and');
  590.   end;
  591.  
  592.   // Insert the criteria at the right place in the basic SQL string.
  593.   // ORDER BY and GROUP BY are supported; UNION is not.
  594.   P := InsensitivePos(sqlGroupBy, FBaseSQL.Text);
  595.   if P <> 0 then
  596.     FOwner.SetSQLText(MergeStr(FBuiltSQL.Text + ' ', FBaseSQL.Text, P))
  597.   else begin
  598.     P := InsensitivePos(sqlOrderBy, FBaseSQL.Text);
  599.     if P <> 0 then
  600.       FOwner.SetSQLText(MergeStr(FBuiltSQL.Text + ' ', FBaseSQL.Text, P))
  601.     else
  602.       FOwner.SetSQLText(FBaseSQL.Text + ' ' + FBuiltSQL.Text);
  603.   end;
  604. end;
  605.  
  606. end.
  607.